#include "common.h"
#include "cpu/reg.h"
#include "memory/cache.h"
#include "nemu.h"
#include "burst.h"
#include <stdlib.h>

uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);

/* Segment */
lnaddr_t seg_translate(swaddr_t addr, size_t len, uint8_t sreg) {
	//printf("here %d\n", cpu.cr0.protect_enable);
	if(cpu.cr0.protect_enable == 0) return addr;
	//printf("%d, %d\n",(int)(addr+len), ((int)cpu.sr[sreg].seg_limit));
	//Assert(addr + len < cpu.sr[sreg].seg_limit, "cs segment out limit");
	return cpu.sr[sreg].seg_base + addr;	
}

/* Page */
#define DIR(addr) ((addr)>>22)
#define PAGE(addr) (((addr)>>12) & 0x3ff)
#define OFFSET(addr) ((addr) & 0xfff)

hwaddr_t TLB_read(uint32_t);
void TLB_write(uint32_t ,uint32_t);

hwaddr_t page_translate(lnaddr_t addr) {
	PAGE_descriptor dir;
	PAGE_descriptor page;
	hwaddr_t hwaddr;
	if(cpu.cr0.protect_enable == 1 && cpu.cr0.paging == 1) {
		if ((hwaddr = TLB_read(addr)) != -1) {
			return hwaddr + OFFSET(addr);
		}
		dir.page_val = hwaddr_read((cpu.cr3.page_directory_base<<12)+(DIR(addr)<<2), 4);
		Assert(dir.p, "pagevalue = %x eip = %x", dir.page_val,cpu.eip);
		page.page_val = hwaddr_read((dir.addr<<12)+(PAGE(addr)<<2), 4);
		Assert(page.p, "page do not exist at %x", cpu.eip);
		hwaddr = (page.addr<<12) + OFFSET(addr);
		TLB_write(addr, hwaddr);
		return hwaddr;
	} else {
		return addr;
	}
}

/* Memory accessing interfaces */
uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	int index = is_mmio(addr);
	if ( index >= 0)
	{
		return mmio_read(addr, len, index);
	}
	uint32_t offset = addr & (BLOCK_SIZE - 1); // inside addr
	uint32_t block = cache_read_l1(addr);
	uint8_t temp[4];
	memset (temp,0,sizeof (temp));

	if (offset + len >= BLOCK_SIZE)
	{
		uint32_t _block = cache_read_l1(addr + len);
		memcpy(temp,cache[block].data + offset, BLOCK_SIZE - offset);
		memcpy(temp + BLOCK_SIZE - offset,cache[_block].data, len - (BLOCK_SIZE - offset));
	}
	else
	{
		memcpy(temp,cache[block].data + offset,len);
	}
	int zero = 0;
	uint32_t tmp = unalign_rw(temp + zero, 4) & (~0u >> ((4 - len) << 3));
	return tmp;
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	int index = is_mmio(addr);
	if ( index >= 0)
	{
		mmio_write(addr, len, data, index);
		return ;
	}
	cache_write_l1(addr, len, data);
}

uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
	#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	size_t max_len = ((~addr) & 0xfff) + 1;
    	if (len > max_len) 
    	{
        		uint32_t low = lnaddr_read(addr, max_len);
        		uint32_t high = lnaddr_read(addr + max_len, len - max_len);
        		return (high << (max_len << 3)) | low;
    	}
	hwaddr_t hwaddr = page_translate(addr);
	return hwaddr_read(hwaddr, len);
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
	#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	size_t max_len = ((~addr) & 0xfff) + 1;
    	if (len > max_len) 
    	{
        		lnaddr_write(addr, max_len, data & ((1 << (max_len << 3)) - 1));
        		lnaddr_write(addr + max_len, len - max_len, data >> (max_len << 3));
        		return;
    	}
	hwaddr_t hwaddr = page_translate(addr);
	hwaddr_write(hwaddr, len, data);
}

uint32_t swaddr_read(swaddr_t addr, size_t len) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr = seg_translate(addr, len, current_sreg);
	return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr = seg_translate(addr, len, current_sreg);
	lnaddr_write(lnaddr, len, data);
}
